package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.ILock;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {


    @Autowired
    private ISeckillVoucherService seckillVoucherService;

    @Autowired
    private RedisIdWorker redisIdWorker;

    @Autowired
    private StringRedisTemplate redisTemplate;



/*    @Transactional
    @Override
    public Result seckill(Long voucherId) {
        // 判断秒杀是否开始，是否结束
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(voucher.getBeginTime())) {
            return Result.fail("秒杀还未开始");
        }

        if (now.isAfter(voucher.getEndTime())) {
            return Result.fail("秒杀已经结束了");
        }

        // 判断库存
        Integer stock = voucher.getStock();
        if (stock <= 0) {
            return Result.fail("库存不足！秒杀失败");
        }
        // 下单，减库存前，先检查一下符不符合一人一单
        // 这样写，在并发的场景下，同一个用户还是可能抢购多次的
        Long userId = UserHolder.getUser().getId();
        Integer count = query().eq("voucher_id", voucherId).eq("user_id", userId).count();
        if (count > 0) {
            return Result.fail("你已经抢购过该秒杀券了，不能重复抢购");
        }
        // 该用户没有抢购过该秒杀券
        // 减库存
//        seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();

        // 通过乐观锁来解决超卖问题
        // 这样cas出错的概率太大了
//        boolean update = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", stock).update();
        // 这样成功的概率就大大提高了
        boolean update = seckillVoucherService.update().setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();
        if (!update) {
            log.info("扣减库存失败，stock与预期值不一致");
            return Result.fail("扣减库存失败，stock与预期值不一致");
        }

        VoucherOrder voucherOrder = new VoucherOrder();
        Long orderId = redisIdWorker.getId("order");
        voucherOrder.setId(orderId);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(userId);
        save(voucherOrder);
        return Result.ok(orderId);
    }*/


    @Override
    public Result seckill(Long voucherId) {
        // 判断秒杀是否开始，是否结束
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        LocalDateTime now = LocalDateTime.now();
        if (now.isBefore(voucher.getBeginTime())) {
            return Result.fail("秒杀还未开始");
        }

        if (now.isAfter(voucher.getEndTime())) {
            return Result.fail("秒杀已经结束了");
        }

        // 判断库存
        Integer stock = voucher.getStock();
        if (stock <= 0) {
            return Result.fail("库存不足！秒杀失败");
        }

        // 但是以上代码还是存在问题，问题的原因在于当前方法被spring的事务控制，
        // 如果你在方法内部加锁，可能会导致当前方法事务还没有提交，但是锁已经释放也会导致问题，
        // 所以我们选择将当前方法整体包裹起来，确保事务不会出现问题

        // 不要锁 this，锁的粒度太粗了，锁userId就行了
//        synchronized (this) {
//            return this.createSeckillVoucherOrder(voucherId);
//        }

        // 但是以上做法依然有问题，因为你调用的方法，其实是this.的方式调用的，
        // 事务想要生效，还得利用代理来生效，所以这个地方，我们需要获得原始的事务对象， 来操作事务
//        Long userId = UserHolder.getUser().getId();
//        synchronized (userId.toString().intern()) {
//            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
//            return proxy.createSeckillVoucherOrder(voucherId);
//        }


        Long id = UserHolder.getUser().getId();
        SimpleRedisLock redisLock = new SimpleRedisLock(redisTemplate, id.toString());
        boolean lock = redisLock.tryLock(5);
        if (!lock) {
            return Result.fail("别想着一个人重复抢购，你没拿到redisLock分布式锁");
        }

        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createSeckillVoucherOrder(voucherId);
        } finally {
            redisLock.unlock();
        }
    }


    @Transactional
    @Override
    public Result createSeckillVoucherOrder(Long voucherId) {
        // 下单，减库存前，先检查一下符不符合一人一单
        // 这样写，在并发的场景下，同一个用户还是可能抢购多次的
        Long userId = UserHolder.getUser().getId();
        Integer count = query()
                .eq("voucher_id", voucherId)
                .eq("user_id", userId)
                .count();
        if (count > 0) {
            return Result.fail("你已经抢购过该秒杀券了，不能重复抢购");
        }
        // 该用户没有抢购过该秒杀券
        // 减库存
//        seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();

        // 通过乐观锁来解决超卖问题
        // 这样cas出错的概率太大了
//        boolean update = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", stock).update();
        // 这样成功的概率就大大提高了
        boolean update = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();

        if (!update) {
            log.info("扣减库存失败，stock与预期值不一致");
            return Result.fail("扣减库存失败，stock与预期值不一致");
        }

        VoucherOrder voucherOrder = new VoucherOrder();
        Long orderId = redisIdWorker.getId("order");
        voucherOrder.setId(orderId);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(userId);
        save(voucherOrder);
        return Result.ok(orderId);
    }


}
